package com.lhz.manager;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import com.lhz.config.ServiceException;
import com.lhz.executor.JobExecutor;
import com.lhz.log.JobLogRecord;
import com.lhz.mapper.JobMapper;
import com.lhz.mapper.JobRegistryMapper;
import com.lhz.model.constant.DataStatusEnum;
import com.lhz.model.constant.JobLogConstant;
import com.lhz.model.constant.RunnableConstant;
import com.lhz.model.constant.TriggerStatusEnum;
import com.lhz.model.entity.JobExecutorContext;
import com.lhz.model.entity.JobInfo;
import com.lhz.model.entity.JobRegistry;
import com.lhz.model.param.JobInfoParam;
import com.lhz.service.ExecutorService;
import com.lhz.service.LogService;
import com.lhz.utils.CronUtils;
import com.lhz.utils.IpUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;

import java.lang.reflect.InvocationTargetException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.stream.Collectors;

/**
 * @Author: LiHuaZhi
 * @Date: 2020/2/29 16:13
 * @Description:
 **/
@Component
@Slf4j
public class JobManager {

    @Value("${server.port}")
    private String port;

    /**
     * 将任务均匀分配到服务上
     */
    private Map<String, List<JobInfo>> map = new HashMap<>(8);

    /**
     * 记录可用执行器数量
     */
    private int size = 0;


    /**
     * 引用自定义配置Bean对象
     */
    @Resource(name = "jobScheduler")
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    @Resource
    private JobMapper jobMapper;

    @Resource
    private JobRegistryMapper jobRegistryMapper;

    @Resource
    WebApplicationContext applicationContext;

    /**
     * 利用springBoot天然的策略模式实现，自动注入JobExecutor的实现类
     */
    @Resource
    private Map<String, JobExecutor> jobExecutorMap;

    @Resource
    private ExecutorService executorService;

    @Resource
    private LogService logService;

    public void registryJob() {
        // 设置当前服务的注册信息
        // 获取当前IP
        String hostIp = IpUtils.getHostIp();
        // 设置注册地址
        String address = JobLogConstant.HTTP + hostIp + ":" + port;
        RunnableConstant.EXECUTOR_ADDRESS = address;

        // 插入注册信息
        JobRegistry jobRegistry = new JobRegistry();
        Long registryId = RandomUtil.randomLong();
        RunnableConstant.EXECUTOR_ID = registryId;
        jobRegistry.setId(registryId);
        jobRegistry.setAddress(address);
        jobRegistry.setRegistryTime(LocalDateTime.now());
        // 先更新
        int update = jobRegistryMapper.updateByAddress(jobRegistry);
        if (update <= 0) {
            jobRegistryMapper.insert(jobRegistry);
        }

        // 健康检测其机器
        executorService.healthJob();
    }

    public void initJob() {
        // 读取自动执行任务列表
        List<JobInfo> jobInfoList = jobMapper.listInfoPage(new JobInfoParam());

        // 获取非中止状态的任务并且不存在任务执行器的任务
        List<JobInfo> startJobs = jobInfoList.stream().filter(t -> t.getStatus().equals(DataStatusEnum.VALID.getCode())).collect(Collectors.toList());

        if (CollectionUtil.isEmpty(startJobs)) {
            return;
        }

        log.info("初始化任务列表：{}", startJobs.size());
        // 执行任务
        for (JobInfo job : startJobs) {
            this.start(job);
        }
    }

    public void destroyJob() {
        log.info("######## 结束所有任务 #########");
        // 清空数据库的注册信息
        jobRegistryMapper.deleteById(RunnableConstant.EXECUTOR_ID);

        // 获取当前机器的任务实例
        List<JobInfo> jobInfoList = new ArrayList<>(RunnableConstant.JOB_MAP.values());

        //查询运行中的任务，进行停止操作
        for (JobInfo jobInfo : jobInfoList) {
            close(jobInfo.getId(), jobInfo.getName());
        }

        // 清空数据
        this.map.clear();
        this.size = 0;
    }

    /**
     * 单次执行任务
     *
     * @param job
     */
    public void invoke(JobInfo job) {
        String executorHandler = job.getExecutorHandler();
        String executorParam = job.getExecutorParam();
        Long jobId = job.getId();
        String jobName = job.getName();
        String address = RunnableConstant.EXECUTOR_ADDRESS;

        // 初始化日志
        JobLogRecord.init(jobId, jobName, address, executorHandler, executorParam);
        RunnableConstant.JOB_MAP.put(jobId, job);
        try {
            invokeJob(jobId, jobName, executorHandler, executorParam);
        } catch (Exception e) {
            String message = e.getMessage();
            JobLogRecord.fail(jobId, message);
            if (e instanceof ServiceException) {
                throw new ServiceException(message);
            }
            log.error(message);
            throw new ServiceException("任务执行失败!");
        } finally {
            JobLogRecord.end(jobId);
            RunnableConstant.JOB_MAP.remove(jobId);
        }
    }

    /**
     * 启动定时任务
     *
     * @param job
     */
    public void start(JobInfo job) {
        Long jobId = job.getId();
        String cron = job.getCron();
        String jobName = job.getName();

        //如果线程已存在则先关闭，再开启新的
        if (RunnableConstant.JOB_SCHEDULED_MAP.get(jobId) != null) {
            log.info("重复任务：" + jobName);
            // close(jobId, jobName);
            return;
        }

        //执行start(自动策略或者人为触发)
        log.info("======== 执行任务：{} ========", jobName);
        // 每次执行时，将读取的下次执行时间，修改为真实的下次执行下级
        LocalDateTime nextCurrentTime = CronUtils.nextCurrentTime(cron);
        job.setTriggerNextTime(nextCurrentTime);
        job.setTriggerStatus(TriggerStatusEnum.RUNNING.getCode());
        JobRunnable jobRunnable = new JobRunnable(job);

        // 将job对象加入内存
        RunnableConstant.JOB_MAP.put(jobId, job);
        // 执行TaskRunnable的run方法
        ScheduledFuture<?> schedule = threadPoolTaskScheduler.schedule(jobRunnable, new CronTrigger(cron));
        RunnableConstant.JOB_SCHEDULED_MAP.put(jobId, schedule);
    }

    /**
     * 关闭的任务
     *
     * @param jobId
     * @param jobName
     */
    public void close(Long jobId, String jobName) {
        ScheduledFuture<?> scheduledFuture = RunnableConstant.JOB_SCHEDULED_MAP.get(jobId);
        if (scheduledFuture != null) {
            scheduledFuture.cancel(true);
            RunnableConstant.JOB_SCHEDULED_MAP.remove(jobId);
            RunnableConstant.JOB_MAP.remove(jobId);
            log.info("关闭任务：{}", jobName);
        }
    }

    public void stop(JobInfo job) {
        Long jobId = job.getId();
        String jobName = job.getName();
        ScheduledFuture<?> scheduledFuture = RunnableConstant.JOB_SCHEDULED_MAP.get(jobId);
        if (scheduledFuture != null) {
            // 调用取消
            // 如果参数为true并且任务正在运行，那么这个任务将被取消
            // 如果参数为false并且任务正在运行，那么这个任务将不会被取消
            scheduledFuture.cancel(true);
            RunnableConstant.JOB_SCHEDULED_MAP.remove(jobId);
            RunnableConstant.JOB_MAP.remove(jobId);
            log.info("停止任务：" + jobName);

            //修改任务状况为停止
            JobInfo jobInfo = new JobInfo();
            jobInfo.setId(jobId);
            // 已暂停
            jobInfo.setTriggerStatus(TriggerStatusEnum.STOP.getCode());
            jobMapper.updateById(jobInfo);
        }
    }

    public void clear() {
        for (Map.Entry<Long, ScheduledFuture<?>> entry : RunnableConstant.JOB_SCHEDULED_MAP.entrySet()) {
            Long jobId = entry.getKey();
            JobInfo jobInfo = RunnableConstant.JOB_MAP.get(jobId);
            stop(jobInfo);
        }
    }

    /**
     * 执行任务
     *
     * @param jobId
     * @param jobName
     * @param executorHandler
     * @param executorParam
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public void invokeJob(Long jobId, String jobName, String executorHandler, String executorParam) {

        // 校验是否存在bean对象
        JobExecutor jobExecutor = jobExecutorMap.get(executorHandler);
        Assert.notNull(jobExecutor, "任务[" + jobName + "]，执行器不存在！");

        // 执行任务
        JobExecutorContext context = new JobExecutorContext();
        context.setJobId(jobId);
        context.setJsonStr(executorParam);
        jobExecutor.run(context);
    }

    /**
     * 检查执行器是否存在
     *
     * @param jobId
     * @param jobName
     * @param executorHandler
     * @param executorParam
     */
    public void checkExecutorHandler(Long jobId, String jobName, String executorHandler, String executorParam) {
        // 校验是否存在bean对象
        JobExecutor jobExecutor = jobExecutorMap.get(executorHandler);
        Assert.notNull(jobExecutor, "任务[" + jobName + "]，执行器不存在！");

        JobExecutorContext context = new JobExecutorContext();
        context.setJobId(jobId);
        context.setJsonStr(executorParam);
        // 检查任务
        boolean check = jobExecutor.check(context);
        if (!check) {
            throw new ServiceException("任务执行器检测异常！");
        }
    }
}